
#include "video_encoder_core.h"
#include "io_data.h"
#include <asm-generic/errno-base.h>
#include <cstddef>
#include <cstdint>
#include <libavcodec/codec.h>
#include <libavcodec/codec_id.h>
#include <libavcodec/packet.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/pixfmt.h>
#include <libavutil/rational.h>

extern "C" {

    #include "libavformat/avformat.h"
    #include "libavcodec/avcodec.h"
    #include "libavutil/avutil.h"
    #include "libavutil/opt.h"
    #include "libavutil/imgutils.h"
}


static const AVCodec * codec = nullptr;
static AVCodecContext * codec_context = nullptr;

static AVCodecParserContext * parser = nullptr;

static AVFrame * frame = nullptr;
static AVPacket * packet = nullptr;



static int32_t encode_frame(bool flushing) {

    int32_t result = 0;
    if (!flushing) {

        std::cout << "Send frame to encoder with pts : " << frame->pts << std::endl;
    }
    result = avcodec_send_frame(codec_context, frame);
    if (result < 0) {

        std::cout << "Error : avcodec_send frame failed." << std::endl;
        return result; 
    }
    while(result >= 0) {

        result = avcodec_receive_packet(codec_context, packet);
        if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {

            return 1;
        
        } else if (result < 0) {

            std::cout << "Error : avocodec_receive_packet failed." << std::endl;
            return result;
        }
        if (flushing) {

            std::cout << "Flushing:";
        }
        std::cout << "Got encoded package with dts : " << packet->dts << ", pts : " << packet->pts << std::endl;
        write_pkt_to_file(packet);
    }
    return 0;
}

static int32_t decode_packet(bool flushing) {

    int32_t result = 0;
    result = avcodec_send_packet(codec_context, packet);
    if (result < 0) {

        std::cout << "Error : Faile to send packet, result : " << result << std::endl;
        return -1;
    }
    while(result >= 0) {

        result = avcodec_receive_frame(codec_context, frame);
        if (result == AVERROR(EAGAIN) || result == AVERROR_EOF) {

            return 1;
        } else if (result < 0) {

            std::cout << "Error: Fail to receive frame, result : " << result << std::endl;
            return -1;
        }
        if (flushing) {

            std::cout << "Flushing:";
        }
        std::cout << "Write frame pic_num:" << frame->coded_picture_number << std::endl;
        write_frame_to_yuv(frame);
    }
    return 0;
}

int32_t init_video_encoder(const std::string &codec_name) {


    if (codec_name.empty()) {

        std::cout << "Error: codec name : " << codec_name << std::endl;
        return -1;
    }
    codec = avcodec_find_encoder_by_name(codec_name.c_str());
    if (!codec) {

        std::cout << "Error: avcodec find encoder by name : " << codec_name << std::endl;
        return -1;
    }
    codec_context = avcodec_alloc_context3(codec);
    if (!codec_context) {

        std::cout << "Error: avcodec alloc content version3" << std::endl;
        return -1;
    }
    // 配置编码参数
    codec_context->profile  = FF_PROFILE_H264_BASELINE;
    codec_context->bit_rate = 2000000;  // 2000kb/s
    codec_context->width    = 1280;
    codec_context->height   = 720;
    codec_context->gop_size = 10;

    codec_context->time_base = (AVRational){1, 25};
    codec_context->framerate = (AVRational){25, 1};
    codec_context->has_b_frames = 0;

    codec_context->pix_fmt = AV_PIX_FMT_YUV420P;

    if (codec->id == AV_CODEC_ID_H264) {

        av_opt_set(codec_context->priv_data, "present", "slow", 0);
    }
    int result = avcodec_open2(codec_context, codec, nullptr);
    if (result < 0) {

        std::cout << "Error: avcodec open version 2 : " << av_err2str(result) << std::endl;
        return -1;
    }
    packet = av_packet_alloc();
    frame  = av_frame_alloc();

    frame->width  = codec_context->width;
    frame->height = codec_context->height;
    frame->format = codec_context->pix_fmt;

    result = av_frame_get_buffer(frame, 0);
    if (result < 0) {

        std::cout << "Error: av frame get buffer : " << av_err2str(result) << std::endl; 
        return -1;
    }
    return 0;
}

int32_t init_video_decoder() {

    codec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!codec) {

        std::cout << "Error : avcodec find decoder failed " << std::endl;
        return -1;
    }
    parser = av_parser_init(codec->id);
    if (!parser) {

        std::cout << "Error : could not init parser." << std::endl;
        return -1;
    }
    codec_context = avcodec_alloc_context3(codec);
    if (!codec_context) {

        std::cout << "Error : could alloc context" << std::endl;
        return -1;
    }
    int32_t result = avcodec_open2(codec_context, codec, nullptr);
    if (result < 0) {

        std::cout << "Error : avocodec open 2 failed : " << av_err2str(result) << std::endl;
        return -1;
    }
    frame = av_frame_alloc();
    if (!frame) {

        std::cout << "Error : could alloc frame struct " << std::endl;
        return -1;
    }
    packet = av_packet_alloc();
    if (!packet) {

        std::cout << "Error : could not alloc packet" << std::endl;
        return -1;
    }
    return 0;
}

void destory_video_decoder() {

    av_parser_close(parser);
    avcodec_free_context(&codec_context);
    av_frame_free(&frame);
    av_packet_free(&packet);
}

void destory_video_encoder() {

    avcodec_free_context(&codec_context);
    av_frame_free(&frame);
    av_packet_free(&packet);
}


int32_t encoding(int32_t frame_cnt) {

    int result = 0;
    for (size_t index = 0; index < frame_cnt; index++) {

        result = av_frame_make_writable(frame);
        if (result) {


            std::cout << "Error : could not av frame make writebale." << std::endl;
            break;
        }
        result = read_yuv_to_frame(frame);
        if (result < 0) {

            std::cout << "Error : read_yuv_to_frame failed." << std::endl;
            break;
        }
        frame->pts = index;

        result = encode_frame(false);
        if (result < 0) {

            std::cout << "Error : encode frame failed." << std::endl;
            break;
        }
    }
    result = encode_frame(true);
    if (result < 0) {

        std::cout << "Error : flushing failed." << std::endl;
    }
    return result;
}

int32_t decoding() {

    uint8_t input_buffer[1500] = {0};
    int32_t result = 0;
    uint8_t * data = nullptr;
    int32_t data_size = 0;

    while (!end_of_input_file()) {

        result = read_data_to_buf(input_buffer, sizeof(input_buffer), data_size);

        if (result < 0) {

            std::cout << "Error : read data to buf failed" << std::endl;
            return -1;
        }
        data = input_buffer;
        while(data_size > 0) {

            result = av_parser_parse2(parser, codec_context, &packet->data, &packet->size, data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
            if (result < 0) {

                std::cout << "Error: av_parser_parse2 failed : " << av_err2str(result) << std::endl;
                return -1;
            }
            data += result;
            data_size -= result;
            if (packet->size > 0) {

                std::cout << "parsed packet size : " << packet->size << std::endl;
                decode_packet(false);
            }
        }
    }
    decode_packet(true);
    return 0;
}
